package org.eli;

import org.eli.hittable.Hittable;
import org.eli.hittable.HittableList;
import org.eli.hittable.Sphere;
import org.eli.material.Dielectric;
import org.eli.material.Lambertian;
import org.eli.material.Material;
import org.eli.material.Metal;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

/**
 * Java 实现光线追踪 -- Ray Tracing in One Weekend V3.0
 *
 * @author eli
 */
public class Main {
    public static void main(String[] args) throws IOException {
        // 照片宽度
        final int imageWidth = 2000;

        // 照片高度
        final int imageHeight = 1000;

        // 像素
        final int samplesPerPixel = 100;

        // 要渲染的深度
        final int maxDepth = 500;

//        HittableList world = new HittableList();
        HittableList world = randomScene();
//        world.add(makeSphere(new Vec3(0, 0, -1), 0.5, new Lambertian(new Vec3(0.7, 0.3, 0.3), 0.1)));
//        world.add(makeSphere(new Vec3(0, -100.5, -1), 100, new Lambertian(new Vec3(0.8, 0.8, 0.0), 0.1)));
//
//        world.add(makeSphere(new Vec3(1, 0, -1), 0.5, new Lambertian(new Vec3(0.8, 0.6, 0.2), 0.1)));
//        world.add(makeSphere(new Vec3(-1, 0, -1), 0.5, new Lambertian(new Vec3(0.8, 0.8, 0.8), 0.1)));

//        world.add(makeSphere(new Vec3(0, 0, -1), 0.5, new Lambertian(new Vec3(0.1, 0.2, 0.5))));
//        world.add(makeSphere(new Vec3(0, -100.5, -1), 100, new Lambertian(new Vec3(0.8, 0.8, 0.0))));
//
//        world.add(makeSphere(new Vec3(1, 0, -1), 0.5, new Metal(new Vec3(0.8, 0.6, 0.2), 0.3)));
//        world.add(makeSphere(new Vec3(-1, 0, -1), 0.5, new Dielectric(1.5)));
//        world.add(makeSphere(new Vec3(-1, 0, -1), -0.45, new Dielectric(1.5)));

        Vec3 lookfrom = new Vec3(13, 2, 3);
        Vec3 lookat = new Vec3(0, 0, 0);
        Vec3 vup = new Vec3(0, 1, 0);
//        double distToFocus  = (lookfrom.subtract(lookat)).length();
        double distToFocus  = 10;
        double aperture = 0.1;
        double aspectRatio = imageWidth / imageHeight;
        Camera cam = new Camera(lookfrom, lookat, vup, 20, aspectRatio, aperture, distToFocus);

        // 创建一个BufferedImage对象来存储图像数据
        BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);

        // 填充图像数据
        for (int j = 0; j < imageHeight; ++j) {
            System.out.println("Scanlines remaining: " + j + " "); // 输出进度信息

            for (int i = 0; i < imageWidth; ++i) {
                Vec3 color = new Vec3(0, 0, 0);
                for (int s = 0; s < samplesPerPixel; ++s) {
                    double u = (i + RtWeekend.randomDouble()) / imageWidth;
                    double v = (j + RtWeekend.randomDouble()) / imageHeight;
                    Ray r = cam.getRay(u, v);
                    color = color.add(rayColor(r, world, maxDepth));
                }
                color.writeColor(image, i, j, samplesPerPixel, imageHeight);
            }
        }

        // 将BufferedImage写入文件
        File outputFile = new File("output.png");
        ImageIO.write(image, "png", outputFile);

        System.out.println("Done."); // 输出完成信息
    }

    public static HittableList randomScene() {
        HittableList world = new HittableList();

        world.add(makeSphere(
                new Vec3(0, -1000, 0), 1000, new Lambertian(new Vec3(0.5, 0.5, 0.5))));

        for (int a = -11; a < 11; a++) {
            for (int b = -11; b < 11; b++) {
                double chooseMat = RtWeekend.randomDouble();
                Vec3 center = new Vec3(a + 0.9 * RtWeekend.randomDouble(), 0.2, b + 0.9 * RtWeekend.randomDouble());
                if ((center.subtract(new Vec3(4, 0.2, 0)).length() > 0.9)) {
                    if (chooseMat < 0.8) {
                        // 漫反射
                        Vec3 albedo = Vec3.random().cross(Vec3.random());
                        world.add(makeSphere(center, 0.2, new Lambertian(albedo)));
                    } else if (chooseMat < 0.95) {
                        // 金属
                        Vec3 albedo = Vec3.random(0.5, 1);
                        double fuzz = RtWeekend.randomDouble(0, 0.5);
                        world.add(makeSphere(center, 0.2, new Metal(albedo, fuzz)));
                    } else {
                        // 玻璃
                        world.add(makeSphere(center, 0.2, new Dielectric(1.5)));
                    }
                }
            }
        }

        world.add(makeSphere(new Vec3(0, 1, 0), 1.0, new Dielectric(1.5)));

        world.add(makeSphere(new Vec3(-4, 1, 0), 1.0, new Lambertian(new Vec3(0.4, 0.2, 0.1))));

        world.add(makeSphere(new Vec3(4, 1, 0), 1.0, new Metal(new Vec3(0.7, 0.6, 0.5), 0.0)));

        return world;
    }

    public static Vec3 rayColor(Ray r, Hittable world, int depth) {
        HitRecord rec = new HitRecord();

        // 如果我们超过了光线反射的极限，就不会再聚集光线了。
        if (depth <= 0) {
            return new Vec3(0,0,0);
        }

        if (world.hit(r, 0.001, RtWeekend.INFINITY, rec)) {
            Ray scattered = new Ray();
            Vec3 attenuation = new Vec3();
            if (rec.matPtr.scatter(r, rec, attenuation, scattered)) {
                return attenuation.multiply(rayColor(scattered, world, depth - 1));
            }
            return new Vec3(0, 0, 0);
        }

        Vec3 unitDirection = unitVector(r.direction());
        double t = 0.5 * (unitDirection.y() + 1.0);
        return unitDirection.scale(new Vec3(1.0, 1.0, 1.0), 1.0 - t).add(unitDirection.scale(new Vec3(0.5, 0.7, 1.0), t));
    }

    public static Vec3 unitVector(Vec3 v) {
        return v.scale(v, 1 / v.length());
    }

    public static Sphere makeSphere(Vec3 center, double radius, Material matPtr) {
        return new Sphere(center, radius, matPtr);
    }
}